#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         AssessmentStorageService.py
#
# Copyright:    Copyright (c) 2004, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  Defines an abstract assessment storage service,
#               of which can be used to manage the storage
#               of Assessments.
#
#               Included in the storage management is the ability
#               to 'place' assessments in 'repositories'.
#
#               A 'repository' is a uniquely named area in which
#               assessments may be stored.  Semantically;
#
#               1. An assessment may belong in zero or more respositories.
#
#               2. Once an assessment is locked, it is locked
#               in all repositories.
#
#               3. Repository Identities may be user/group identities.
#
#               4. There are four special repositories; 
#                  'anonymous', 'unknown', 'archived', 'garbage'.
#
#               An example implementation of this class is
#               the MySQLAssessmentStorageService.
#
#               NOTE: Implementations DO NOT manage NOR are they
#               meant to manage security or events for assessments.
#               This MUST be done by the calling clients of this
#               interface.
#
#               NOTE: Services are typically implemented as
#               'Plone Tools'
#
# See Also:     Assessment, Assessible, 
#               AssessmentComponentState, AssessmentComponentValueTypes
#####################################################################

#####################################################################
## KnowledgeEngine Library Imports
from Common import *
from Assessible import Assessible
from Assessment import Assessment
from AssessmentComponentState import AssessmentComponentState
from AssessmentComponentValueTypes import *
from Service import ServiceInfo, Service
from Products.KnowledgeEngine.Model.Model import Model

#####################################################################
## Zope Library Imports
from Products.CMFCore.utils import getToolByName
 
#####################################################################
## Python Library Imports
from time import time, gmtime, asctime, localtime

#####################################################################
## AssessmentStorageServiceInfo Class Definition
class AssessmentStorageServiceInfo(ServiceInfo):
    """
        Defines the assessment storage service meta-information
    """
    
    #---------------------------------------------------------------#
    
    def getClassName(self):
        """
            Returns the name of the class for which 
            we are providing information.
        """
        return 'AssessmentStorageService'


    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """
    
        return "KnowledgeEngine: Assessment Storage Service"
        
    #---------------------------------------------------------------#

    def getServiceName(self):
        """
            ABSTRACT: Return the lookup-identity.
            
            This method must be overriden to provide a unique service name
            (this name is used to set the zope id of the service
            when created)
        """
        
        return "knowledgeengine_assessmentstorageservice"

#####################################################################
## AssessmentStorageService Class Definition
class AssessmentStorageService(Service):
    """
        Defines a tool that may be used to manage the storage of 
        assessments.
    """

    #---------------------------------------------------------------#

    #ABSTRACT TRANSIENT: a ServiceInfo implementation instance to provide info about this service
    _SERVICEINFO = AssessmentStorageServiceInfo()

    #---------------------------------------------------------------#

    def __init__(self):
        """
            Constructor for the object
        """
        
        #this is a stateless mix-in
        pass
        
    #---------------------------------------------------------------#
        
    def createAssessment(self, model, userIdentity = None, repositoryIdentity = None):
        """
            Creates and returns an new Assessment instance
            for the specified model and assessor.
            
            NOTE: Does not initially persist the assessment.  To 
            persist a new assessment you must call 'saveAssessment'
            on this interface
            
            RETURN TYPE: Assessment
        """
        
        return Assessment(generateIdentity("aid"), model, userIdentity, repositoryIdentity)
        
    #---------------------------------------------------------------#
        
    def saveAssessment(self, assessment):
        """
            ABSTRACT: Saves the specified assessment and updates
            it's current in-memory state (so that it isn't new or modified)
        """
        
        #implementation to be provided in sub-class
        pass
        
    #---------------------------------------------------------------#
        
    def loadAssessment(self, assessmentIdentity, model, userIdentity = None, repositoryIdentity = None):
        """
            ABSTRACT: Loads the assessment with the specified
            assessment and model from storage.
            
            NOTE 1: Returns None if the assessment is unknown
            
            NOTE 2: Does not necessarily load all of the component
            state for all Assessible components or Assessment
            Attributes in the model.
            
            RETURN TYPE: Assessment
        """        
        
        #implementation to be provided in sub-class
        pass
        
    #---------------------------------------------------------------#
        
    def checkoutAssessment(self, assessment, user):
        """
            ABSTRACT: Attempts to checkout the specified assessment
            for the specified model and user
            
            If successful, the checkedOutBy property of the returned
            assessment will equal user.getIdentity()
            
            Otherwise either the assessment is in use by another user
            or the model supports concurrent assessment.
            
            RETURN TYPE: Assessment
        """
        
        return None
        
    #---------------------------------------------------------------#
        
    def checkinAssessment(self, assessment, user):
        """
            ABSTRACT: Attempts to checkin the specified assessment.
            
            If successful, the checkedOutBy property of the returned
            assessment will be None.  
            
            Otherwise the assessment is in use by another user or
            it supports concurrent assessment
            
            RETURN TYPE: Assessment
        """
            
        pass            
        
    #---------------------------------------------------------------#
        
    def deleteAssessment(self, assessment):
        """
            Deletes the specified assessment from storage
            (including within repositories)

            NOTE: If the specifed assessment is new, nothing happens.
        """
        
        #this is a proxy
        self.deleteAssessmentWith(assessment.getIdentity(), assessment.getModel().getIdentity())
         
    #---------------------------------------------------------------#
        
    def deleteAssessmentWith(self, assessmentIdentity, modelIdentity):
        """
            ABSTRACT: Deletes the specified assessment from storage
            (including within repositories)
        """
        
        #implementation to be provided in sub-class
        pass
        
    #---------------------------------------------------------------#

    def loadComponentState(self, assessment, componentIdentity):
        """
            ABSTRACT: Loads the component state for the specified
            component and returns the AssessmentComponentState.
            
            NOTE 1: Does not modify the assessment
            
            NOTE 2: Returns None if the component state doesn't exist
            or could not be loaded
        
            RETURN TYPE: AssessmentComponentState
        """
        
        return None
        
    #---------------------------------------------------------------#

    def existsComponentState(self, assessment, componentIdentity):
        """
            ABSTRACT: Returns if persistent state exists for the
            specified component for the provided assessment.
            
            NOTE: This method checks the underlying persistent storage
            for the specified component's state.  It does not check 
            the 'cached' assessment for the component state.  That is,
            this method will return 'false' if the underlying 
            persistent storage does not contain the component state
            even if the provided assessment contains the component
            state.
            
            RETURN TYPE: Boolean
        """            
            
        #implementation to be provided in sub-class
        return false          
        
    #---------------------------------------------------------------#

    def deleteAllComponentState(self, component):
        """
            ABSTRACT: Deletes all of the component state for the
            specified component.
            
            This method is called when a component is deleted to
            clean up the assessment state for the said component
        """
        
        #implementation to be provided in sub-class
        pass
            
    #---------------------------------------------------------------#

    def saveComponentState(self, assessmentComponentState, instantModified = None):
        """
            ABSTRACT: Stores the specified assessment component state
            
            instantModified (optional) a DateTime instance that 
            is used to force the modification time of the component state
            to a specified value
            
            (generally called by self.saveAssessment)
        """
        
        #implementation to be provided in sub-class
        pass
        
    #---------------------------------------------------------------#

    def loadAttribute(self, assessment, attributeHandle):
        """
            ABSTRACT: Loads the attribute for the specified
            component and returns the result as an 
            AssessmentAttribute representation.
            
            NOTE 1: Does not modify the assessment
            
            NOTE 2: Returns None if the attribute doesn't exist
            or could not be loaded
        
            RETURN TYPE: AssessmentAttribute
        """
        
        return None
        
    #---------------------------------------------------------------#

    def existsAttribute(self, assessment, attributeHandle):
        """
            ABSTRACT: Returns if persistent attribute state exists for the
            specified attribute in the provided assessment.
            
            NOTE: This method checks the underlying persistent storage
            for the specified attributes's state.  It does not check 
            the 'cached' assessment for the attribute.  That is,
            this method will return 'false' if the underlying 
            persistent storage does not contain the attribute
            even if the provided assessment contains the component
            state.
            
            RETURN TYPE: Boolean
        """            
            
        #implementation to be provided in sub-class
        return false          
        
    #---------------------------------------------------------------#

    def deleteAllAttributes(self, attributeHandle):
        """
            ABSTRACT: Deletes all of the attributes with the 
            specified attributeHandle.
            
            This method is called when a GenericObject is deleted 
            (that uses attributes) to clean up the assessment storage
        """
        
        #implementation to be provided in sub-class
        pass
            
    #---------------------------------------------------------------#

    def saveAttribute(self, attribute, instantModified = None):
        """
            ABSTRACT: Stores the specified assessment attribute
            
            instantModified (optional) a DateTime instance that 
            is used to force the modification time of the attribute
            to a specified value
            
            (generally called by self.saveAssessment)
        """
        
        #implementation to be provided in sub-class
        pass
            
    #---------------------------------------------------------------#

    def deleteAssessmentsForModel(self, model):
        """
            ABSTRACT: Deletes all of the assessments for 
            the specified model (including assessments
            within repositories)
        """
        
        #implementation to be provided in sub-class
        pass
            
    #---------------------------------------------------------------#

    def deleteAllAssessments(self):
        """
            ABSTRACT: Deletes all of the assessments that are 
            stored using this assessment storage tool (including assessments
            within repositories)
        """
        
        #implementation to be provided in sub-class
        pass
        
    #---------------------------------------------------------------#

    def addAssessmentToRepository(self, assessment, repositoryIdentity):
        """
            ABSTRACT: Adds the specified assessment to the
            repository with the specified identity.  If the assessment 
            is already in the repository, nothing happens.
        """
        
        pass
        
    #---------------------------------------------------------------#

    def removeAssessmentFromRepository(self, assessment, repositoryIdentity):
        """
            ABSTRACT: Removes the specified assessment from the repository
            with the specified identity.  If the assessment is not
            in the repository, nothing happens.
        """
        
        pass
        
    #---------------------------------------------------------------#

    def removeAssessmentFromRepositories(self, assessment):
        """
            ABSTRACT: Removes the specified assessment from 
            all of the repository in which it is located.
            
            If the assessment is not in a repository, nothing happens.
        """
        
        pass
        
    #---------------------------------------------------------------#

    def removeAssessmentsFromRepository(self, model, repositoryIdentity):
        """
            ABSTRACT: Removes all assessments from the specified model 
            repository.
            
            Does nothing if the repository doesn't exist
        """
        
        pass
                
    #---------------------------------------------------------------#

    def isAssessmentInRepository(self, assessment, repositoryIdentity):
        """
            ABSTRACT: Returns if the specified assessment is in the
            repository specified by the repository identity.
        """
        
        return false
        
    #---------------------------------------------------------------#

    def getNrAssessmentsInRepository(self, model, repositoryIdentity):
        """
            ABSTRACT: Returns an the number of assessments in the 
            specified model and repository.
            
            RETURN TYPE: int
        """ 
        
        return 0
        
    #---------------------------------------------------------------#

    def getInstantAssessmentJoinedRepository(self, assessment, repositoryIdentity):
        """
            ABSTRACT: Return the instant in time (since the Epoc) 
            when the assessment was added to the specified respository
            
            RETURNS: -1 if the assessment is not in the respository
            
            RETURN TYPE: int
        """ 
        
        return -1
        
    #---------------------------------------------------------------#

    def getTimeAssessmentJoinedRepository(self, assessment, repositoryIdentity):
        """
            FINAL: Returns the time (in ascii format) 
            when the assessment was added to the specified respository
            
            RETURNS: "" if the assessment is not in the respository
            
            RETURN TYPE: String
        """ 
        
        return asctime(gmtime(self.getInstantAssessmentJoinedRepository(assessment, repositoryIdentity)))
        
    #---------------------------------------------------------------#

    def getRepositoriesForAssessment(self, assessment):
        """
            ABSTRACT: Returns a list of repositoryIdentities
            in which the specified assessment is located.
        """
        
        return []
        
    #---------------------------------------------------------------#

    def getNrAssessmentsInRepository(self, model, repositoryIdentity):
        """
            ABSTRACT: Returns the number of assessments 
            for the specified repository
        """

        return 0
        
    #---------------------------------------------------------------#

    def getAssessmentsInRepository(self, model, repositoryIdentity, batchNr = None, batchSize = 30, sortBy = None):
        """
            ABSTRACT: Returns a list of [Assessment] 
            in the specified model and repository.
            
            NOTE: If a batch number is specified, only the specified
            'batchSize' number of assessments are returned.  Otherwise
            all repository assessments are returned.

            RETURN TYPE: [Assessment]
        """ 
        
        return []
        
    #---------------------------------------------------------------#

    def getAssessmentsInRepositoryWithScope(self, model, repositoryIdentity, scope, authenticatedUserIdentity):
        """
            ABSTRACT: Returns the assessments 
            for the specified repository with in the specified scope, possibly
            using the authenticatedUser object (User)
            
            Returns assessments that have been created by users that are in the specified
            scope list that are also in the specified repository.
            
            Parameters:
            scope : [String] - Is a list of actorIdentities that created the assessments
            authenticatedUserIdentity : String - Is a knowledge engine user
        """

        return []
        
    #---------------------------------------------------------------#

    def isAssessmentInScope(self, assessment, scope, authenticatedUserIdentity):
        """
            ABSTRACT: Returns if the specified assessment is in the specified scope, possibly
            using the authenticatedUser object (User)

            RETURNS: Boolean
            
            Parameters:
            assessment : Assessment - An Assessment
            scope : [String] - Is a list of actorIdentities that created the assessments
            authenticatedUserIdentity : String - Is a knowledge engine user
        """

        return false
        
    #---------------------------------------------------------------#

    def getModels(self):
        """
            FINAL: Returns a list of the Models in the portal
        """
        
        #get the portal catalog
        catalog = getToolByName(self, "portal_catalog")
        
        #search for Models using the meta_type
        searchResults = catalog(meta_type = Model.meta_type)
        
        #results aren't the actual objects, so lets reference them
        models = []
        for searchResult in searchResults:
            #attempt to resolve the actual object reference
            model = searchResult.getObject()
            
            #only add models that truely exist!
            if model is not None:
                models = models + [model]
                
        return models
        
    #---------------------------------------------------------------#

    def getModel(self, modelIdentity):
        """
            FINAL: Returns the model with the specified identity
            or None if not found
        """
        
        models = [model for model in self.getModels() if model.getIdentity() == modelIdentity]
        
        if len(models) == 0:
            return None
        else:
            return models[0]
        
    #---------------------------------------------------------------#

    def getNrAssessmentsForModel(self, model):
        """
            ABSTRACT: Returns the number of assessments 
            for the specified model (regardless of the repository)
        """
        
        return 0
        
    #---------------------------------------------------------------#

    def getAssessmentsForModel(self, model, batchNr = None, batchSize = 30, sortBy = None):
        """
            ABSTRACT: Returns the assessments for the specified
            model (regardless of the repository)
            
            NOTE: If a batch number is specified, only the specified
            'batchSize' number of assessments are returned.  Otherwise
            all assessments are returned.

            RETURN TYPE: [Assessment]
        """
        
        return []
        
    #---------------------------------------------------------------#

    def getNrAssessmentsForModelAndUser(self, model, user):
        """
            ABSTRACT: Returns the number of assessments 
            for the specified model and user (regardless of the repository)
        """
        
        return 0
        
    #---------------------------------------------------------------#

    def getAssessmentsForModelAndUser(self, model, user, batchNr = None, batchSize = 30, sortBy = None):
        """
            ABSTRACT: Returns the assessments for the specified
            model and user (regardless of the repository)
            
            NOTE: If a batch number is specified, only the specified
            'batchSize' number of assessments are returned.  Otherwise
            all assessments are returned.

            RETURN TYPE: [Assessment]
        """
        
        return []
        
    #---------------------------------------------------------------#

    def getNrAssessmentsSatisfyingFilter(self, assessmentFilter, user):
        """
            ABSTRACT: Returns the number of assessments that satisfy 
            the provided assessment filter and user
            
            NOTE: Does not take kerl transitivity into account or 
            showif rules that may cause values to be invalid.
            
            PARAMETERS:
            assessmentFilter : AssessmentFilter
            user : User - A Knowledge Engine User instance
            
            Assessment scoping provided as part of the AssessmentFilter.
            
            RETURN TYPE: NUMBER
        """            

        return 0
        
    #---------------------------------------------------------------#

    def getAssessmentsSatisfyingFilter(self, assessmentFilter, user, batchNr = None, batchSize = 30, sortBy = None):
        """
            ABSTRACT: Returns the assessments that satisfy the 
            provided assessment filter and user
            
            NOTE 1: Does not take kerl transitivity into account or 
            showif rules that may cause values to be invalid.
            
            NOTE 2: If a batch number is specified, only the specified
            'batchSize' number of assessments are returned.  Otherwise
            all assessments are returned that satisfy the filter.
            
            PARAMETERS:
            assessmentFilter : AssessmentFilter
            user : User - A Knowledge Engine User instance
            
            Assessment scoping provided as part of the AssessmentFilter.
            
            RETURN TYPE: [Assessment]
        """            
        
        return []
        
    #---------------------------------------------------------------#

#####################################################################
## Class Initialisation
# (none)
